package com.change.core.redis;

import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis
 */
@Slf4j
public final class Redis {

    private Redis() {
    }

    private static RedisTemplate redisTemplate;

    private static RedisTemplate getRedisTemplate() {
        if(redisTemplate == null) {
            redisTemplate = SpringUtil.getBean("redisTemplate");
            if (redisTemplate == null) {
                throw new RuntimeException("请务必保证spring容器中存在RedisTemplate的实例");
            }
        }
        return redisTemplate;
    }

    public static boolean expire(String key, long time) {
        try {
            if (time > 0) {
                getRedisTemplate().expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 批量删除对应的value
     *
     * @param keys
     */
    public static void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 根据所有符合给定模式 pattern 的 key 批量删除key
     *
     * @param pattern
     */
    public static void removePattern(final String pattern) {
        RedisTemplate redisTemplate = getRedisTemplate();
        Set<String> keys = redisTemplate.keys(pattern);
        if (keys.size() > 0) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * 批量删除key
     *
     * @param keys
     */
    public static Long remove(final Collection keys) {
        return getRedisTemplate().delete(keys);
    }

    /**
     * 删除对应的value
     *
     * @param key
     */
    public static void remove(final String key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 判断缓存中是否有对应的value
     *
     * @param key
     * @return
     */
    public static boolean exists(final String key) {
        return getRedisTemplate().hasKey(key);
    }

    /**
     * 查找所有符合给定模式 pattern 的 key
     *
     * @param pattern
     * @return
     */
    public static Set getKeys(String pattern) {
        return getRedisTemplate().keys(pattern);
    }

    /**
     * 读取缓存
     *
     * @param key
     * @return
     */
    public static <T> T get(final String key) {
        return (T) getRedisTemplate().opsForValue().get(key);
    }

    /**
     * 根据多个key批量获取redis缓存
     *
     * @param key
     * @param <T>
     * @return
     */
    public static <T> List<T> multiGet(final Collection key) {
        ValueOperations<String, Object> operations = getRedisTemplate().opsForValue();
        Object result = operations.multiGet(key);
        if (result != null) {
            return (List<T>) result;
        }
        return null;
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public static boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<String, Object> operations = getRedisTemplate().opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            log.error("set cache error", e);
        }
        return result;
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @param expireTime 秒
     * @return
     */
    public static boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<String, Object> operations = getRedisTemplate().opsForValue();
            operations.set(key, value);
            getRedisTemplate().expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            log.error("set cache error", e);
        }
        return result;
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @param expireDay 天
     * @return
     */
    public static boolean set(final String key, Object value, int expireDay) {
        boolean result = false;
        try {
            ValueOperations<String, Object> operations = getRedisTemplate().opsForValue();
            operations.set(key, value);
            getRedisTemplate().expire(key, expireDay, TimeUnit.DAYS);
            result = true;
        } catch (Exception e) {
            log.error("set cache error", e);
        }
        return result;
    }

    /**
     * 批量写入缓存
     *
     * @param value
     * @return
     */
    public static boolean multiSet(final Map value) {
        boolean result = false;
        try {
            ValueOperations<String, Object> operations = getRedisTemplate().opsForValue();
            operations.multiSet(value);
            result = true;
        } catch (Exception e) {
            log.error("set cache error", e);
        }
        return result;
    }

    /**
     * 批量写入缓存
     *
     * @param value
     * @param expireTime 秒 7 * 24 * 60 * 60 * 1000 = 7天
     * @return
     */
    public static boolean multiSet(final Map value, long expireTime) {
        boolean result = false;
        try {
            ValueOperations<String, Object> operations = getRedisTemplate().opsForValue();
            operations.multiSet(value);
            // 使用管道更新缓存过期时间 TODO 管道非原子性 集群下可能导致更新不准确
            getRedisTemplate().executePipelined(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    for (Object key : value.keySet()) {
                        redisConnection.pExpire(key.toString().getBytes(), expireTime);
                    }
                    return null;
                }
            });
            result = true;
        } catch (Exception e) {
            log.error("set cache error", e);
        }
        return result;
    }

    public static long increment(final String key, long delta) {
        return getRedisTemplate().opsForValue().increment(key, delta);
    }

    /**
     * 写入哈希缓存
     *
     * @param key
     * @param value
     * @return
     */
    public static boolean putAll(final String key, Map value) {
        boolean result = false;
        try {
            getRedisTemplate().opsForHash().putAll(key, value);
            result = true;
        } catch (Exception e) {
            log.error("putAll cache error", e);
        }
        return result;
    }

    /**
     * 写入hashSet
     *
     * @param key
     * @param hk
     * @param hv
     * @return
     */
    public static boolean putHashSet(final String key, String hk, Object hv) {
        boolean result = false;
        try {
            getRedisTemplate().opsForHash().put(key, hk, hv);
            result = true;
        } catch (Exception e) {
            log.error("putAll cache error", e);
        }
        return result;
    }

    /**
     * 写入hashS并设置过期时间,天
     *
     * @param key
     * @param hk
     * @param hv
     * @return
     */
    public static boolean putHashSet(final String key, String hk, Object hv, Integer day) {
        boolean result = false;
        try {
            getRedisTemplate().opsForHash().put(key, hk, hv);
            getRedisTemplate().expire(key, day, TimeUnit.DAYS);
            result = true;
        } catch (Exception e) {
            log.error("putAll cache error", e);
        }
        return result;
    }

    /**
     * 读取hashSet
     *
     * @param key
     * @param hk
     * @return
     */
    public static <T> T getHashSet(final String key, String hk) {
        try {
            return (T) getRedisTemplate().opsForHash().get(key, hk);
        } catch (Exception e) {
            log.error("putAll cache error", e);
        }
        return null;
    }

    /**
     * 删除哈希缓存
     *
     * @param key
     * @param hash
     */
    public static void remove(final String key, int hash) {
        try {
            getRedisTemplate().opsForHash().delete(key, hash);
        } catch (Exception e) {
            log.error("putAll cache error", e);
        }
    }

    /**
     * 指定的 key 不存在时，为 key 设置指定的值。存在则设置失败
     * 设置成功，返回 1 。 设置失败，返回 0
     *
     * @param key     键
     * @param value   值
     * @param timeOut 锁存在时间
     * @return
     */
    public static Boolean setnx(final String key, final Object value, long timeOut) {
        return getRedisTemplate().opsForValue().setIfAbsent(key, value, timeOut, TimeUnit.SECONDS);
    }

    /**
     * 指定的 key 不存在时，为 key 设置指定的值。存在则设置失败
     * 设置成功，返回 1 。 设置失败，返回 0
     *
     * @param key     键
     * @param value   值
     * @param timeOut 锁存在时间(毫秒)
     * @return
     */
    public static Boolean setNxMilliseconds(final String key, final Object value, long timeOut) {
        return getRedisTemplate().opsForValue().setIfAbsent(key, value, timeOut, TimeUnit.MILLISECONDS);
    }


    /**
     * 进入哈希缓存
     *
     * @param key
     * @return
     */
    public static Map entries(final Object key) {
        try {
            return getRedisTemplate().opsForHash().entries(key);
        } catch (Exception e) {
            log.error("putAll cache error", e);
        }
        return new HashMap();
    }

    /**
     * 执行lua脚本
     *
     * @param script lua脚本
     * @param keys   键
     * @param value  值
     * @return
     */
    public static Object executeLuaScript(RedisScript script, List<String> keys, Object... value) {
        return getRedisTemplate().execute(script, keys, value);
    }

    /**
     * 获取指定key的剩余生存时间，单位秒
     *
     * @param key
     * @return
     */
    public static Long getExpire(Object key) {
        return getRedisTemplate().getExpire(key);
    }

    /**
     * 获取指定key的剩余生存时间
     *
     * @param key      key
     * @param timeUnit 返回的时间单位
     * @return
     */
    public static Long getExpire(Object key, TimeUnit timeUnit) {
        return getRedisTemplate().getExpire(key, timeUnit);
    }

}
